import SPI;
import Credential;
import Util;
import OpenApiUtil;
import EncodeUtil;
import SignatureUtil;
import String;
import Map;
import Array;

extends SPI;

init() {
  super();
}

async function modifyConfiguration(context: SPI.InterceptorContext, attributeMap: SPI.AttributeMap): void {
  var config = context.configuration;
  config.endpoint = getEndpoint(config.network, config.endpoint);
}

async function modifyRequest(context: SPI.InterceptorContext, attributeMap: SPI.AttributeMap): void {
  var request = context.request;
  var config = context.configuration;
  var date = Util.getDateUTCString();
  request.headers = {
    date = date,
    host = config.endpoint,
    x-acs-version = request.version,
    x-acs-action = request.action,
    user-agent = request.userAgent,
    x-acs-signature-nonce = Util.getNonce(),
    accept = 'application/json',
    ...request.headers
  };

  var signatureAlgorithm : string = Util.defaultString(request.signatureAlgorithm, 'ACS4-HMAC-SHA256');
  var signatureVersion = Util.defaultString(request.signatureVersion, 'v1');
  var hashedRequestPayload = EncodeUtil.hexEncode(EncodeUtil.hash(Util.toBytes(''), signatureAlgorithm));

  if (!Util.isUnset(request.stream)) {
    var tmp = Util.readAsBytes(request.stream);
    hashedRequestPayload = EncodeUtil.hexEncode(EncodeUtil.hash(tmp, signatureAlgorithm));
    request.stream = tmp;
    request.headers.content-type = 'application/octet-stream';
  } else {
    if (!Util.isUnset(request.body)) {
      if (Util.equalString(request.reqBodyType, 'json')) {
        var jsonObj = Util.toJSONString(request.body);
        hashedRequestPayload = EncodeUtil.hexEncode(EncodeUtil.hash(Util.toBytes(jsonObj), signatureAlgorithm));
        request.stream = jsonObj;
        request.headers.content-type = 'application/json; charset=utf-8';
      } else {
        var m = Util.assertAsMap(request.body);
        var formObj = OpenApiUtil.toForm(m);
        hashedRequestPayload = EncodeUtil.hexEncode(EncodeUtil.hash(Util.toBytes(formObj), signatureAlgorithm));
        request.stream = formObj;
        request.headers.content-type = 'application/x-www-form-urlencoded';
      }
    }
  }

  if (String.equals(signatureVersion, 'v4')){
    if (Util.equalString(signatureAlgorithm, 'ACS4-HMAC-SM3')) {
      request.headers.x-acs-content-sm3 = hashedRequestPayload;
    } else {
      request.headers.x-acs-content-sha256 = hashedRequestPayload;
    }
  } else {
    request.headers.x-acs-signature-method = 'HMAC-SHA1';
    request.headers.x-acs-signature-version = '1.0';
  }

  if (!Util.equalString(request.authType, 'Anonymous') && !Util.isUnset(request.credential)) {
    var credential : Credential = request.credential;
    var credentialModel = credential.getCredential();
    var authType = credentialModel.type;
    if (Util.equalString(authType, 'bearer')) {
      var bearerToken = credentialModel.bearerToken;
      request.headers.x-acs-bearer-token = bearerToken;
      request.headers.Authorization = `Bearer ${bearerToken}`;
    } else {
      var accessKeyId = credentialModel.accessKeyId;
      var accessKeySecret = credentialModel.accessKeySecret;
      var securityToken = credentialModel.securityToken;
      if (!Util.empty(securityToken)) {
        request.headers.x-acs-security-token = securityToken;
      }

      var headers : map[string]string = {};
      if (!Util.isUnset(request.headers.content-type)) {
        headers = request.headers;
      } else if (String.equals(request.reqBodyType, 'formData') && String.equals(request.action, 'DownloadFile') && String.equals(request.pathname, '/v2/file/download')) {
        var headersArray : [string] = Map.keySet(request.headers);
        for(var key : headersArray) {
          headers[key] = request.headers[key];
        } 
        headers.content-type = 'application/x-www-form-urlencoded; charset=UTF-8';
      } else {
          headers = request.headers;
      }
      if (String.equals(signatureVersion, 'v4')) {
        var dateNew = String.subString(date, 0, 10);
        var region = getRegion(config.endpoint);
        var signingkey = getSigningkey(signatureAlgorithm, accessKeySecret, region, dateNew);
        request.headers.Authorization = getAuthorizationV4(request.pathname, request.method, request.query, headers, signatureAlgorithm, hashedRequestPayload, accessKeyId, signingkey, request.productId, region, dateNew);
      } else {
        request.headers.Authorization = getAuthorization(request.pathname, request.method, request.query, headers, accessKeyId, accessKeySecret);
      } 
    }
  }
}

async function modifyResponse(context: SPI.InterceptorContext, attributeMap: SPI.AttributeMap): void {
  var request = context.request;
  var response = context.response;
  if (Util.is4xx(response.statusCode) || Util.is5xx(response.statusCode)) {
    var _res = Util.readAsJSON(response.body);
    var err = Util.assertAsMap(_res);
    var headers : map[string]string = response.headers;
    var requestId = headers.x-ca-request-id;
    err.statusCode = response.statusCode;
    err.requestId = requestId;
    throw {
      code = `${defaultAny(err.Code, err.code)}`,
      message = `${defaultAny(err.Message, err.message)}`,
      data = err,
    };
  }

  if (!Util.isUnset(response.body)) {
    if (Util.equalNumber(response.statusCode, 204)) {
      Util.readAsString(response.body);
    } else if (Util.equalString(request.bodyType, 'binary')) {
      response.deserializedBody = response.body;
    } else if (Util.equalString(request.bodyType, 'byte')) {
      var byt = Util.readAsBytes(response.body);
      response.deserializedBody = byt;
    } else if (Util.equalString(request.bodyType, 'string')) {
      var str = Util.readAsString(response.body);
      response.deserializedBody = str;
    } else if (Util.equalString(request.bodyType, 'json')) {
      response.deserializedBody = Util.readAsJSON(response.body);
    } else if (Util.equalString(request.bodyType, 'array')) {
      response.deserializedBody = Util.readAsJSON(response.body);
    } else {
      response.deserializedBody = Util.readAsString(response.body);
    }
  }
}

async function getEndpoint(network: string, endpoint: string) : string{
  var realEndpoint = 'api.aliyunpds.com';
  if (!Util.empty(endpoint)) {
    realEndpoint = endpoint;
  }
  if (!Util.empty(network) && String.equals(network, 'vpc')) {
    realEndpoint = String.replace(realEndpoint, 'api.aliyunpds.com', 'api-vpc.aliyunpds.com', null);
    realEndpoint = String.replace(realEndpoint, 'admin.aliyunpds.com', 'admin-vpc.aliyunpds.com', null);
  }
  return realEndpoint;
}

function defaultAny(inputValue: any, defaultValue: any): any {
  if (Util.isUnset(inputValue)) {
    return defaultValue;
  }
  return inputValue;
}

async function getAuthorization(pathname: string, method: string, query: map[string]string, headers: map[string]string, ak: string, secret: string): string {
  var signature = getSignature(pathname, method, query, headers, secret);
  return `acs ${ak}:${signature}`;
}

async function getSignature(pathname: string, method: string, query: map[string]string, headers: map[string]string, secret: string): string {
  var stringToSign : string = '';
  var canonicalizedResource = buildCanonicalizedResource(pathname, query);
  var canonicalizedHeaders = buildCanonicalizedHeaders(headers);
  stringToSign = `${method}\n${canonicalizedHeaders}${canonicalizedResource}`;
  var signature = SignatureUtil.HmacSHA1Sign(stringToSign, secret);
  return EncodeUtil.base64EncodeToString(signature);
}

async function buildCanonicalizedResource(pathname: string, query: map[string]string): string {
  var canonicalizedResource : string = pathname;
  if (!Util.isUnset(query)) {
    var queryArray : [string] = Map.keySet(query);
    var sortedQueryArray = Array.ascSort(queryArray);
    var separator : string = '?';
    for(var key : sortedQueryArray) {
      canonicalizedResource = `${canonicalizedResource}${separator}${key}`;
      if (!Util.empty(query[key])) {
        canonicalizedResource = `${canonicalizedResource}=${query[key]}`;
      }
      separator = '&';
    }
  }
  return canonicalizedResource;
}

async function buildCanonicalizedHeaders(headers: map[string]string): string {
  var accept = headers.accept;
  if (Util.isUnset(accept)) {
    accept = '';
  }
  var contentMd5 = headers.content-md5;
  if (Util.isUnset(contentMd5)) {
    contentMd5 = '';
  }
  var contentType = headers.content-type;
  if (Util.isUnset(contentType)) {
    contentType = '';
  }
  var date = headers.date;
  if (Util.isUnset(date)) {
    date = '';
  }
  var canonicalizedHeaders : string = `${accept}\n${contentMd5}\n${contentType}\n${date}\n`;
  var sortedHeaders : [string] = getSignedHeaders(headers);
  for(var header : sortedHeaders) {
    var value = headers[header];
    var valueTrim = String.trim(value);
    canonicalizedHeaders = `${canonicalizedHeaders}${header}:${valueTrim}\n`;
  }
  return canonicalizedHeaders;
}

async function getSignedHeaders(headers: map[string]string): [string] {
  var headersArray : [string] = Map.keySet(headers);
  var sortedHeadersArray = Array.ascSort(headersArray);
  var tmp : string = '';
  var separator : string = '';
  for(var key : sortedHeadersArray) {
    var lowerKey = String.toLower(key);
    if (String.hasPrefix(lowerKey, 'x-acs-')) {
      if (!String.contains(tmp, lowerKey)) {
        tmp = `${tmp}${separator}${lowerKey}`;
        separator = ';';
      }
    }
  }
  return String.split(tmp, ';', null);
}

function getRegion(endpoint: string): string {
  var region = 'center';
  if (Util.empty(endpoint)) {
    return region;
  }
  if (String.contains(endpoint, '.admin.aliyunpds.com')) {
    region = String.replace(endpoint, '.admin.aliyunpds.com', '', null);
  }
  return region;
}

async function getSigningkey(signatureAlgorithm: string, secret: string, region: string, date: string): bytes {
  var sc1 = `aliyun_v4${secret}`;
  var sc2 = Util.toBytes('');
  if (Util.equalString(signatureAlgorithm, 'ACS4-HMAC-SHA256')) {
    sc2 = SignatureUtil.HmacSHA256Sign(date, sc1);
  } else if (Util.equalString(signatureAlgorithm, 'ACS4-HMAC-SM3')) {
    sc2 = SignatureUtil.HmacSM3Sign(date, sc1);
  }
  var sc3 = Util.toBytes('');
  if (Util.equalString(signatureAlgorithm, 'ACS4-HMAC-SHA256')) {
    sc3 = SignatureUtil.HmacSHA256SignByBytes(region, sc2);
  } else if (Util.equalString(signatureAlgorithm, 'ACS4-HMAC-SM3')) {
    sc3 = SignatureUtil.HmacSM3SignByBytes(region, sc2);
  }
  var sc4 = Util.toBytes('');
  if (Util.equalString(signatureAlgorithm, 'ACS4-HMAC-SHA256')) {
    sc4 = SignatureUtil.HmacSHA256SignByBytes('pds', sc3);
  } else if (Util.equalString(signatureAlgorithm, 'ACS4-HMAC-SM3')) {
    sc4 = SignatureUtil.HmacSM3SignByBytes('pds', sc3);
  }
  var hmac = Util.toBytes('');
  if (Util.equalString(signatureAlgorithm, 'ACS4-HMAC-SHA256')) {
    hmac = SignatureUtil.HmacSHA256SignByBytes('aliyun_v4_request', sc4);
  } else if (Util.equalString(signatureAlgorithm, 'ACS4-HMAC-SM3')) {
    hmac = SignatureUtil.HmacSM3SignByBytes('aliyun_v4_request', sc4);
  }
  return hmac;
}

async function getAuthorizationV4(pathname: string, method: string, query: map[string]string, headers: map[string]string, signatureAlgorithm: string, payload: string, ak: string, signingkey: bytes, product: string, region: string, date: string): string {
  var signature = getSignatureV4(pathname, method, query, headers, signatureAlgorithm, payload, signingkey);
  var signedHeaders = getSignedHeaders(headers);
  var signedHeadersStr = Array.join(signedHeaders, ';');
  return `${signatureAlgorithm} Credential=${ak}/${date}/${region}/${product}/aliyun_v4_request,SignedHeaders=${signedHeadersStr},Signature=${signature}`;
}

async function getSignatureV4(pathname: string, method: string, query: map[string]string, headers: map[string]string, signatureAlgorithm: string, payload: string, signingkey: bytes): string {
  var stringToSign : string = '';
  var canonicalizedResource = buildCanonicalizedResource(pathname, query);
  var canonicalizedHeaders = buildCanonicalizedHeaders(headers);
  var signedHeaders = getSignedHeaders(headers);
  var signedHeadersStr = Array.join(signedHeaders, ';');
  stringToSign = `${method}\n${canonicalizedResource}\n${canonicalizedHeaders}\n${signedHeadersStr}\n${payload}`;
  var hex = EncodeUtil.hexEncode(EncodeUtil.hash(Util.toBytes(stringToSign), signatureAlgorithm));
  stringToSign = `${signatureAlgorithm}\n${hex}`;
  var signature = Util.toBytes('');
  if (Util.equalString(signatureAlgorithm, 'ACS4-HMAC-SHA256')) {
    signature = SignatureUtil.HmacSHA256SignByBytes(stringToSign, signingkey);
  } else if (Util.equalString(signatureAlgorithm, 'ACS4-HMAC-SM3')) {
    signature = SignatureUtil.HmacSM3SignByBytes(stringToSign, signingkey);
  }
  return EncodeUtil.hexEncode(signature);
}